﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.IO;
using System.Net;
using System.Xml;
using System.Web;
namespace WebServer.Models
{

    

    class GoogleDirectionsParser
    {
        #region Private Vars

        // doc is the internal representation of the xml
        // document to be retrieved from google maps
        // using the Google Maps Directions API
        private XDocument xdoc;
        // Used to give specific errors back to the user
        // without popping two message boxes up
        private String errorMessage;


        private List<string> durations { get; set; }
        private List<string> distances { get; set; }
        private TimeSpan waitingTime { get; set; }

        #endregion

        #region public vars

        public String startAddress { get; private set; }
        public String endAddress { get; private set; }
        public TimeSpan totalDuration { get; private set; }
        public Double totalDistance { get; private set; }
        public List<string> instructions { get; private set; }
        public List<string> waypoints { get; private set; }
        public Dictionary<string, TimeSpan> waypointTimes { get; private set; }
        public String mapUrl { get; private set; }
        #endregion


        #region Ctor
        /// <summary>
        /// Default Constructor
        /// </summary>
        public GoogleDirectionsParser()
        {
            xdoc = new XDocument();
            mapUrl = string.Empty;
            errorMessage = string.Empty;
        }
        #endregion

        #region GetDirections()
        /// <summary>
        /// Retrieves routes and map of route from google maps
        /// </summary>
        /// <param name="origin">Starting Location</param>
        /// <param name="destination">End Location</param>
        /// <param name="pathToHtmlFile">Load saved Html file in web browser control - Should be (Application.StartupPath + htmlFile)</param>
        /// <param name="pathToSaveXml">Path to store downloaded XML</param>		
        /// <returns>True - Success, False - Error</returns>
        public bool GetDirections(string origin, string destination)// string pathToHtmlFile, string pathToSaveXml = "temp.xml")
        {
            startAddress = string.Empty;
            endAddress = string.Empty;
            totalDuration = new TimeSpan(0);
            totalDistance = 0.0;
            instructions = new List<string>();
            durations = new List<string>();
            distances = new List<string>();

            if (!DownloadXml(origin, destination))
            {
                throw new Exception(String.Format("{1} - {0}", errorMessage, "Error downloading XML"));
            }

            if (!ParseXml())
            {
                throw new Exception(String.Format("{1} - {0}", errorMessage, "Error parsing XML"));
            }

            return true;
        }

        public bool GetDirections(string origin, List<string> stopsOnRoute, string finalDestination, TimeSpan waitingTimeAtEachStop)// string pathToHtmlFile, string pathToSaveXml = "temp.xml")
        {

            string nearestNeighbour = origin;
            waypoints = new List<string>();

            //Calculate the route: nearest neighbour first
            while (stopsOnRoute.Count > 0)
            {
                nearestNeighbour = GetNearestNeighbour(nearestNeighbour, stopsOnRoute);
                if (nearestNeighbour == null || nearestNeighbour.Equals(""))
                {
                    throw new Exception(String.Format("Error finding nearest neighbour - Could not find a nearest neighbour."));
                }

                stopsOnRoute.Remove(nearestNeighbour);
                waypoints.Add(nearestNeighbour);

            }

            startAddress = string.Empty;
            endAddress = string.Empty;
            totalDuration = new TimeSpan(0);
            totalDistance = 0.0;
            instructions = new List<string>();
            durations = new List<string>();
            distances = new List<string>();

            if (!DownloadXmlRouteWithWayPoints(origin, waypoints, finalDestination))
            {
                throw new Exception(String.Format("{1} - {0}", errorMessage, "Error downloading XML"));
            }

            if (!ParseXml())
            {
                throw new Exception(String.Format("{1} - {0}", errorMessage, "Error parsing XML"));
            }

            waypointTimes = new Dictionary<string, TimeSpan>();

            if (0 < waypoints.Count && 0 < durations.Count)
            {
                waypointTimes.Add(waypoints[0], new TimeSpan(0, 0, int.Parse(durations[0])));

                for (int i = 1; i < waypoints.Count && i < durations.Count; i++)
                {
                    TimeSpan thisStopSpan = new TimeSpan(0, 0, int.Parse(durations[i]));
                    TimeSpan previousStopSpan = waypointTimes[waypoints[i-1]];

                    waypointTimes.Add(waypoints[i], thisStopSpan.Add(previousStopSpan).Add(waitingTime));
                }

                TimeSpan lastStopSpan = new TimeSpan(0, 0, int.Parse(durations[durations.Count-1]));
                waypointTimes.Add(finalDestination, lastStopSpan.Add(waitingTime).Add(waypointTimes[waypoints[waypoints.Count-1]]));

            }
            endAddress = finalDestination;
            startAddress = origin;

            String mapPoints = "";
            for(int i = 0; i < waypoints.Count(); i++)
            {
                if (mapPoints.Equals(""))
                {
                    mapPoints = waypoints[i];
                }
                else
                {
                    mapPoints += " to:" + waypoints[i];
                }
            }
            if (mapPoints.Equals(""))
            {
                mapPoints = endAddress;
            }
            else
            {
                mapPoints += "+to:" + endAddress;
            }

            mapUrl = String.Format(@"https://maps.google.com/maps?saddr={0}&daddr={1}",startAddress,mapPoints);

            return true;
        }


        #endregion

        #region GetDirections() helper functions

        private string GetNearestNeighbour(string origin, List<string> neighbours)
        {
            startAddress = string.Empty;
            endAddress = string.Empty;
            totalDuration = new TimeSpan(0);
            totalDistance = 0.0;
            instructions = new List<string>();
            durations = new List<string>();
            distances = new List<string>();

            Dictionary<string, double> neighbourDistances = new Dictionary<string, double>();

            //Calculate in which order to take the routes: nearest neighbour first
            for (int i = 0; i < neighbours.Count; i++)
            {
                if (!DownloadXml(origin, neighbours[i]))
                {
                    throw new Exception(String.Format("{1} - {0}", errorMessage, "Error downloading XML"));
                }

                if (!ParseXml())
                {
                    throw new Exception(String.Format("{1} - {0}", errorMessage, "Error parsing XML"));
                }


                neighbourDistances.Add(neighbours[i], totalDistance);


            }

            return neighbourDistances.OrderBy(w => w.Value).First().Key;
        }


        private bool DownloadXml(string origin, string destination)
        {
            WebClient web = new WebClient();
            try
            {//
                byte[] xmlBytes = web.DownloadData("http://maps.googleapis.com/maps/api/directions/xml?origin=" + origin + "&destination=" + destination + "&sensor=false");
                string xml = Encoding.UTF8.GetString(xmlBytes);

                xdoc = XDocument.Parse(xml);
            }
            catch (Exception ex)
            {
                errorMessage = ex.Message;
                return false;
            }
            return true;
        }

        private bool DownloadXmlRouteWithWayPoints(string origin, List<String> waypoints, string finalDestination)
        {
            WebClient web = new WebClient();
            try
            {
                byte[] xmlBytes = web.DownloadData(String.Format("http://maps.googleapis.com/maps/api/directions/xml?origin={0}&destination={1}&waypoints={2}&sensor=false",
                    origin,
                    finalDestination,
                    String.Join("|", waypoints.ToArray())
                    ));
                string xml = Encoding.UTF8.GetString(xmlBytes);

                xdoc = XDocument.Parse(xml);
            }
            catch (Exception ex)
            {
                errorMessage = ex.Message;
                return false;
            }
            return true;
        }


        /// <summary>
        /// Parse Xml document and store the routes and map
        /// </summary>
        /// <returns>True - Successful, False - Error</returns>
        private bool ParseXml()
        {
            try
            {
                // Make sure <status> = OK
                string status = (from step in xdoc.Descendants("status")
                                 select step.Value).ToList<string>()[0];

                if (status != "OK")
                {
                    errorMessage = "No route can be mapped out";
                    return false;
                }

                // Get start and end address
                startAddress = (from step in xdoc.Descendants("start_address")
                                select step.Value).ToList<string>()[0];
                endAddress = (from step in xdoc.Descendants("end_address")
                              select step.Value).ToList<string>()[0];

                // Get route for map
                string polyLine = (from step in xdoc.Descendants("overview_polyline")
                                   select step.Element("points").Value).ToList<string>()[0];


                mapUrl = "";

                // Get all the instructions synced
                instructions = (from step in xdoc.Descendants("html_instructions")
                                select step.Value).ToList();
                durations = (from step in xdoc.Descendants("leg").Elements("duration")
                             select step.Element("value").Value).ToList();
                distances = (from step in xdoc.Descendants("leg").Elements("distance")
                             select step.Element("value").Value).ToList();
                
                // Get total duration and distance
                // Then Remove them
                for (int i = 0; i < durations.Count && i < distances.Count; i++)
                {
                    totalDuration = totalDuration.Add(new TimeSpan(0, 0, int.Parse(durations[i])));
                    totalDistance += double.Parse(distances[i]);
                     
                }

                totalDistance /= 1000; //getting in km.

            }
            catch (Exception ex)
            {
                errorMessage = ex.Message;
                return false;
            }
            return true;
        }

        #endregion


    }


}
